/******************************************************************************

    Copyright (C) 2006 Alessio Stalla (alessiostalla@gmail.com)

    This file is part of PrettyProlog.

    PrettyProlog is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    PrettyProlog is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with PrettyProlog; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    See the file COPYING for further details.

 *****************************************************************************/

package prettyprolog.engine.syspreds;

import java.util.List;
import java.util.Vector;

import prettyprolog.datatypes.Callable;
import prettyprolog.datatypes.Constant;
import prettyprolog.datatypes.Namespace;
import prettyprolog.datatypes.Real;
import prettyprolog.datatypes.Substitution;
import prettyprolog.datatypes.Term;
import prettyprolog.datatypes.Variable;
import prettyprolog.engine.Engine;
import prettyprolog.engine.UnificationAlgorithm;

/**
 * Implements the functor/3 system predicate. functor(Fn, F, N):
 * <ol>
 * <li>If Fn is a variable, F a constant, and N a number, unifies Fn with a
 * Callable with name F and N arguments;</li>
 * <li>if Fn is a Callable, unifies F with Fn's name and N with Fn's number of
 * arguments;</li>
 * <li>if all the three parameters are bound to a value, succeeds if Fn is a
 * Callable with name F and N arguments;</li>
 * <li>in any other case, prints an error message and fails.</li>
 * </ol>
 */
public class Functor extends SystemPredicate {

	private static final Functor inst = new Functor();

	private Functor() {
		super(3);
	}

	/** Returns the only instance of this system predicate. */
	public static Functor getInstance() {
		return inst;
	}

	public SyspredEffect doCall(Engine en, Substitution subst, List<Term> args)
			throws SyspredException {
		Term fn = args.get(0);
		Term f = args.get(1);
		Term n = args.get(2);
		if ((fn instanceof Variable) && (f instanceof Constant)
				&& (n instanceof Real)) { // Case 1
			Substitution tmp_subst = new Substitution();
			int nargs = (int) ((Real) n).getValue();
			List<Term> fargs = new Vector<Term>(nargs);
			// Produce some pretty argument names
			String pr = Namespace.getGensymPrefix();
			int symcnt = Namespace.getGensymCount();
			Namespace.setGensymPrefix("_ARG");
			Namespace.setGensymCount(1);
			for (int i = 0; i < nargs; i++)
				fargs.add(new Variable(Namespace.gensym()));
			// Restore things as they were
			Namespace.setGensymPrefix(pr);
			Namespace.setGensymCount(symcnt);

			Callable c = new Callable(((Constant) f).getSymbol(), fargs);
			tmp_subst.putMapping((Variable) fn, c);
			return new SyspredEffect(false, subst.compose(tmp_subst));
		} else if (fn instanceof Callable) { // Cases 2 & 3
			UnificationAlgorithm u = en.getUnificationAlgorithm();
			Substitution tmp_subst = u.unify(f,
					new Constant(((Callable) fn).getSymbol()));
			if (tmp_subst == null)
				return new SyspredEffect(true, subst);
			subst = subst.compose(tmp_subst);
			n = n.apply(tmp_subst);
			tmp_subst = u.unify(n, new Real(((Callable) fn).getArgCount()));
			if (tmp_subst == null)
				return new SyspredEffect(true, subst);
			subst = subst.compose(tmp_subst);
			return new SyspredEffect(false, subst);
		} else {
			en.getIOManager().error(
					"functor: not enough arguments are instantiated");
			return new SyspredEffect(true, subst);
		}
	}

	public boolean isBacktrackable() {
		return false;
	}

	public String toString() {
		return super.toString() + " functor/3";
	}
}